﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using Babylon.Primitives;
using XnaTools;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Silverlight;
using System.IO;
using SStuff.XnaInterop;
using Babylon.Toolbox;
using System.Linq;
using System.Windows.Threading;
using Babylon.Particles;
using System.Windows.Media.Imaging;

namespace ViperSample
{
    public class Scene : BaseScene
    {
        private float _width;
        private float _height;
        OrbitCamera _camera;
        private InteropModel _viperModel;
        StatesManager _stateManager;
        TextBlock _frameTime, _counters;
        Dispatcher _dispatcher;
        public Scene(DrawingSurface drawingSurface, FrameworkElement inputSurface, Dispatcher dispatcher, TextBlock frameTime, TextBlock counters) : base(drawingSurface, inputSurface)
        {
            _frameTime = frameTime;
            _counters = counters;
            _dispatcher = dispatcher;
            _camera = new OrbitCamera() { Radius = 20 };
            _camera.NearPlane = 0.1f;
            inputSurface.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(inputSurface_MouseLeftButtonDown);
            inputSurface.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(inputSurface_MouseLeftButtonUp);
            inputSurface.MouseMove += new System.Windows.Input.MouseEventHandler(inputSurface_MouseMove);
            inputSurface.MouseWheel += new System.Windows.Input.MouseWheelEventHandler(inputSurface_MouseWheel);
        }
        Point _startPos;
        void inputSurface_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
        {
            _camera.Radius -= e.Delta * _camera.Radius / 1000.0f;
        }
        bool _mouseDown;
        void inputSurface_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            if (!_mouseDown)
                return;

            Point currentPosition = e.GetPosition(InputSurface);
            _camera.InertialAlpha += (float)(currentPosition.X - _startPos.X) * _camera.AngularSpeed;
            _camera.InertialBeta -= (float)(currentPosition.Y - _startPos.Y) * _camera.AngularSpeed;


            _startPos = currentPosition;
        }

        void inputSurface_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            _mouseDown = false;
        }

        void inputSurface_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            _mouseDown = true;
            _startPos = e.GetPosition(InputSurface);
        }

        BasicEffect[] _meshEffects;
        
        ParticleSystem _ray1, _ray2, _ray3, _fire1, _fire2, _fire3;
        private DateTime _lastFrameTime;
        protected override void OnLoaded(object sender, RoutedEventArgs e){
            _lastFrameTime = DateTime.Now;
            var gd = GraphicsDeviceManager.Current.GraphicsDevice;
            using (var viperStream = Application.GetResourceStream(new Uri("Models/viper.bin", UriKind.Relative)).Stream)
            using (var reader = new BinaryReader(viperStream))
            {
                _viperModel = InteropModel.FromBinaryReader(reader);
                _viperModel.Prepare(gd);
            }

            _meshEffects = new BasicEffect[_viperModel.Meshes.Length];
            for (int i = 0; i < _meshEffects.Length; i++)
            {
                var effect = new BasicEffect(gd);
                _meshEffects[i] = effect;
                effect.AmbientColor = (_viperModel.Meshes[i].Parts[0].EffectParameters.Where(p => p.ParameterName == "AmbientColor").First()
                    .Data).First().ToColor();
                effect.DiffuseColor = (_viperModel.Meshes[i].Parts[0].EffectParameters.Where(p => p.ParameterName == "DiffuseColor").First()
                    .Data).First().ToColor();
                effect.SpecularColor = (_viperModel.Meshes[i].Parts[0].EffectParameters.Where(p => p.ParameterName == "SpecularColor").First()
                    .Data).First().ToColor();
                effect.EmissiveColor = (_viperModel.Meshes[i].Parts[0].EffectParameters.Where(p => p.ParameterName == "EmissiveColor").First()
                    .Data).First().ToColor();

                effect.DiffuseTexture = _viperModel.Meshes[i].Parts[0].IndexedTextureUsages["DiffuseTexture"].SL5Texture;
                effect.BumpTexture = _viperModel.Meshes[i].Parts[0].IndexedTextureUsages["NormalMap"].SL5Texture;
                effect.SpecularTexture = _viperModel.Meshes[i].Parts[0].IndexedTextureUsages["SpecularMap"].SL5Texture;
            }

            BasicEffect cubeEffect = new BasicEffect(gd);
            cubeEffect.DiffuseTexture = ContentManager.LoadTexture2D(gd,
                                                                     Application.GetResourceStream(
                                                                         new Uri("ViperRay.png", UriKind.Relative)).
                                                                         Stream, false);
            _ray1 = new ParticleSystem(gd, CreateViperRaySettings(cubeEffect.DiffuseTexture), new Vector3(13.5f, 1.66f, 0));
            _ray2 = new ParticleSystem(gd, CreateViperRaySettings(cubeEffect.DiffuseTexture), new Vector3(13.5f, -0.05f, -2.01f));
            _ray3 = new ParticleSystem(gd, CreateViperRaySettings(cubeEffect.DiffuseTexture), new Vector3(13.5f, -0.04f, 2.03f));


            _fire1 = CreateFire(gd, new Vector3(13.5f, 1.66f, 0));
            _fire2 = CreateFire(gd, new Vector3(13.5f, -0.05f, -2.01f));
            _fire3 = CreateFire(gd, new Vector3(13.5f, -0.04f, 2.03f));


        }

        ParticleSettings CreateViperRaySettings(Texture2D texture)
        {
            return  new ParticleSettings()
            {
                BlendState = BlendState.Additive,
                EndVelocityRatio = 1.2f,
                EnvironmentForces = new Vector3(0),
                MaxEndSize = 2.2f,
                MaxParticleCount = 540,
                MaxRotationSpeed = 0,
                MaxStartSize = 1.8f,
                MaxTeint = new Vector3(1,1,1),
                MaxVelocityValues = new Vector3(30, 0.3f,0.3f),
                MinEndSize = 1.8f,
                MinRotationSpeed = 0,
                MinStartSize = 1.5f,
                MinTeint = new Vector3(0.2f,0.2f,1.0f),
                MinVelocityValues = new Vector3(15,-0.3f,-0.3f),
                ParticleAvgDuration = 3.0f,
                ParticleDurationRandomness = 0.2f,
                Texture = texture                
            };
        }

        static ParticleSystem CreateFire(GraphicsDevice gd, Vector3 position)
        {
            ParticleSettings fireSettings = new ParticleSettings();

            Texture2D fireTexture = LoadTexture("fire.png", gd);

            fireSettings.MaxParticleCount = 60;
            fireSettings.Texture = fireTexture;
            fireSettings.BlendState = BlendState.Additive;
            fireSettings.ParticleAvgDuration = 0.3f;
            fireSettings.ParticleDurationRandomness = 0.5f;
            fireSettings.MinStartSize = 1.3f;
            fireSettings.MaxStartSize = 1.6f;
            fireSettings.MinEndSize = 3.5f;
            fireSettings.MaxEndSize = 4.5f;
            fireSettings.MinVelocityValues = new Vector3(2.0f, 0, 0);
            fireSettings.MaxVelocityValues = new Vector3(2.5f, 0, 0);
            fireSettings.EndVelocityRatio = 1;
            fireSettings.MinRotationSpeed = -1;
            fireSettings.MaxRotationSpeed = 1;
            fireSettings.EnvironmentForces = new Vector3(0.5f, 0, 0);
            fireSettings.MinTeint = new Vector3(1, 0.4f, 0.0f);
            fireSettings.MaxTeint = new Vector3(1, 0.9f, 0.0f);

            return new ParticleSystem(gd, fireSettings, position);
        }

        static Texture2D LoadTexture(string resource, GraphicsDevice gd)
        {
            var stream = Application.GetResourceStream(new Uri(resource, UriKind.Relative)).Stream;
            var bmp = new BitmapImage();
            bmp.SetSource(stream);


            Texture2D res = new Texture2D(gd, bmp.PixelWidth, bmp.PixelHeight, false, SurfaceFormat.Color);
            bmp.CopyTo(res);
            return res;
        }

        protected override void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            _width = (float)e.NewSize.Width;
            _height = (float)e.NewSize.Height;
            _camera.AspectRatio = _width / _height;
        }


        protected override void OnDraw(object sender, DrawEventArgs e)
        {
            var gd = e.GraphicsDevice;

            DateTime start = DateTime.Now;
            if (_stateManager == null)
                _stateManager = new StatesManager(gd);

            // States
            _stateManager.Device = gd;
            _stateManager.DepthBufferEnable = true;
            _stateManager.CullMode = CullMode.None;
            _stateManager.ApplyDepthStencilState();
            _stateManager.ApplyRasterizerState();
            e.GraphicsDevice.BlendState = BlendState.AlphaBlend;

            gd.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, new Color(0, 0, 0, 255), 1, 0);
           
            _ray1.Device = gd;
            _ray2.Device = gd;
            _ray3.Device = gd;
            _fire1.Device = gd;
            _fire2.Device = gd;
            _fire3.Device = gd;

            var ellapsed = (float)e.TotalTime.TotalSeconds;


            for (int i = 0; i < _meshEffects.Length; i++)
            {
                _meshEffects[i].Device = gd;
                _meshEffects[i].SceneAmbientColor = new Color(0.3f, 0.3f, 0.3f, 0);
                _meshEffects[i].World = Matrix.Identity;
                _meshEffects[i].View = _camera.View;
                _meshEffects[i].Projection = _camera.Projection;
                _meshEffects[i].LightPosition = _camera.Position + new Vector3(1, 5, 3);
                _meshEffects[i].CameraPosition = _camera.Position;
            }

            for (int i = 0; i < _viperModel.Meshes.Length; i++)
            {
                var mesh = _viperModel.Meshes[i];
                    var effect = _meshEffects[i];
                    effect.Apply();
                for (int j = 0; j < mesh.Parts.Length; j++)
                {
                    var part = mesh.Parts[j];
                    part.Draw(gd);
                }
            }

            _ray1.UpdateAndDraw(gd, ellapsed, _camera.View, _camera.Projection);
            _ray2.UpdateAndDraw(gd, ellapsed, _camera.View, _camera.Projection);
            _ray3.UpdateAndDraw(gd, ellapsed, _camera.View, _camera.Projection);
            _fire1.UpdateAndDraw(gd, ellapsed, _camera.View, _camera.Projection);
            _fire2.UpdateAndDraw(gd, ellapsed, _camera.View, _camera.Projection);
            _fire3.UpdateAndDraw(gd, ellapsed, _camera.View, _camera.Projection);
            _ray1.AddParticles(3, ellapsed);
            _ray2.AddParticles(3, ellapsed);
            _ray3.AddParticles(3, ellapsed);
            _fire1.AddParticles(1, ellapsed);
            _fire2.AddParticles(1, ellapsed);
            _fire3.AddParticles(1, ellapsed);

            _camera.ApplyInertia();
            double duration = (double)DateTime.Now.Subtract(start).Ticks / TimeSpan.TicksPerMillisecond;
            double lastFrameDuration = (double) (start - _lastFrameTime).Ticks/TimeSpan.TicksPerMillisecond;
            _lastFrameTime = start;
            int verticesCount = _viperModel.VertexBuffers.Sum(vb=>vb.VertexCount);
            _dispatcher.BeginInvoke(() =>
            {
                _frameTime.Text = string.Format("Frame time : {0:0.00} ms / {1:0.00} fps. Real fps is {2:0.00}", duration, 1000 / duration, 1000 / lastFrameDuration);
                _counters.Text = string.Format("Vertices count : {0}", verticesCount);
            });
            e.InvalidateSurface();
        }
    }
    internal static class  Tools
    {
        public static Color ToColor(this Vector4 v)
        {
            return new Color(v.X, v.Y, v.Z, v.W);
        }
    }
}
